
/*
 *  
 *
 *  Copyright (C) Microsoft Corporation.  All rights reserved
 * 
 *  Config file read + parse
 *  Registry Cache management.
 */


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32; // For registry
using System.Collections.Specialized;
using System.Xml;
using System.Net;

namespace CRMScenarios
{
    /// <summary>
    /// Class for the operations on confiuration file.
    /// </summary>
    public static class ConfigFileManager
    {
        static string strLocationConfigFile = null;
        static XmlDocument xmlDocConfigFile = null;
        static bool boolUseConfigFile = false;

        /// <summary>
        /// Get the server URL, 
        /// Read the file to a DOM
        /// </summary>
        static ConfigFileManager()
        {
            string srvUrl  = CacheManager.GerServerURL(string.Empty);

            strLocationConfigFile = srvUrl + "/CRM Add-in/Config.xml";
            string configuration = null;

            try
            {
                WebRequest myWebRequest = WebRequest.Create(strLocationConfigFile);
                myWebRequest.Credentials = CredentialCache.DefaultCredentials;
                WebResponse myWebResponse = myWebRequest.GetResponse();
                System.IO.Stream ReceiveStream = myWebResponse.GetResponseStream();
                Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
                // Pipe the stream to a higher level stream reader with the required encoding format. 
                System.IO.StreamReader readStream = new System.IO.StreamReader(ReceiveStream, encode);
                configuration = readStream.ReadToEnd();                
            }
            catch (Exception)
            { }

            
            try
            {
                xmlDocConfigFile = Util.CreateXmlDocument(configuration);//.LoadXml(configuration);
                boolUseConfigFile = true;
            }
            catch (Exception)
            {
                // reading in config file error.
            }
        }

        /// <summary>
        /// Parse and get the specified node and the value.
        /// </summary>
        /// <param name="configName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        private static int GetSysConfigDetails(string configName, int defaultValue)
        {
            int retVal = defaultValue;
            if ((boolUseConfigFile == false) || (configName == null) || (configName == string.Empty))
            {
                return retVal;
            }
            XmlNode node = xmlDocConfigFile.SelectSingleNode("//CrmOfficeConfig/SysConfig/" + configName);
            if (node != null)
            {
                string retValStr = node.InnerText;
                try
                {
                    retVal = Convert.ToInt32(retValStr);
                }
                catch { }
            }
            return retVal;
        }

        /// <summary>
        /// Get MaxRecordCount
        /// </summary>
        /// <param name="defVal"></param>
        /// <returns></returns>
        public static int MaxRecCountInGrid(int defVal)
        {
            return GetSysConfigDetails("MaxRecordCount", defVal);
        }

        /// <summary>
        /// Get CacheEntryCount
        /// </summary>
        /// <param name="defVal"></param>
        /// <returns></returns>
        public static int CacheEntryLimit(int defVal)
        {
            return GetSysConfigDetails("CacheEntryCount", defVal);
        }

        /// <summary>
        /// Get MetaDataRefreshTime
        /// </summary>
        /// <param name="defVal"></param>
        /// <returns></returns>
        public static int GetMetaDataTimeOut(int defVal)
        {
            return GetSysConfigDetails("MetaDataRefreshTime", defVal);
        }

        /// <summary>
        /// Get the list of excluded entities
        /// </summary>
        /// <returns></returns>
        public static StringCollection GetEntityExclusionList2()
        {
            if (boolUseConfigFile == false)
            {
                return null;
            }
            XmlNodeList xmlNlistEntities = xmlDocConfigFile.SelectNodes("//CrmOfficeConfig/Entities/entity");
            StringCollection strCollEntityList = new StringCollection();

            foreach (XmlNode xmlNodeEntity in xmlNlistEntities)
            {
                if ((xmlNodeEntity.Attributes.GetNamedItem("IsExcluded").InnerText.ToLower() == "yes"))
                {
                    strCollEntityList.Add(xmlNodeEntity.Attributes.GetNamedItem("name").InnerText);
                }               
            }
            return strCollEntityList; 
        }

        /// <summary>
        /// Get the list of excluded attributes/relatedtyped fot a given entitytype
        /// </summary>
        /// <returns></returns>
        public static StringCollection GetExclusionList2(string entityName, string selection)
        {
            if ( (boolUseConfigFile == false) || (entityName == null) || (selection == null))
            {
                return null;
            }
            XmlNode entity = xmlDocConfigFile.SelectSingleNode("//CrmOfficeConfig/Entities/entity[@name='" + entityName + "']");           
            StringCollection strCollEntityList = new StringCollection();
            if ((entity == null) || (!entity.HasChildNodes))
            {
                return strCollEntityList;
            }
            foreach (XmlNode xmlNodeEntity in entity.ChildNodes)
            {
                if ((xmlNodeEntity.Name == selection) && ((xmlNodeEntity.Attributes.GetNamedItem("IsExcluded").InnerText.ToLower() == "yes")))
                {
                    strCollEntityList.Add(xmlNodeEntity.Attributes.GetNamedItem("name").InnerText);
                }
            }
            return strCollEntityList; 
        }


        /// <summary>
        /// DONT USE -BUGGY
        /// </summary>
        /// <returns></returns>
        public static string[] GetEntityExclusionList()
        {
            string [] returnList = null;
            if (boolUseConfigFile == false)
            {
                return null;
            }
            XmlNodeList xmlNlistEntities ;

            // GET THE SCHEMA CONFIG FILE REVIEWED
            // ONE IDEA COULD BE TO HAVE FOLLOWING FORMAT
            // 
                //<Entities>
                //    <Entity name="Account" IsExcluded="false">
                //        <Attribute name="BillingAddress" IsExcluded="false" />
                //    </Entity>
                //</Entities>
            //
            xmlNlistEntities  = xmlDocConfigFile.SelectNodes("/CrmOfficeConfig/Entities/entity/");
            StringCollection strCollEntityList = new StringCollection();

            foreach(XmlNode xmlNodeEntity in xmlNlistEntities)
            {
                if (xmlNodeEntity.HasChildNodes == false)
                {
                    strCollEntityList.Add(xmlNodeEntity.Attributes.GetNamedItem("name").ToString());
                }
            }

            returnList = new string[strCollEntityList.Count];
            strCollEntityList.CopyTo(returnList, 0);
            return returnList;            
        }


        /// <summary>
        /// DONT USE -BUGGY
        /// </summary>
        /// <returns></returns>
        public static string [] GetExclusionList(string entityName, string selection)
        {
            if ((boolUseConfigFile == false)||(entityName == null))
            {
                return null;
            }
            string [] returnList = null;

            XmlNode xmlNEntity;

            string strXpath = string.Format("/CrmOfficeConfig/Entities/entity[@name='{0}']", entityName); 
            xmlNEntity = xmlDocConfigFile.SelectSingleNode(strXpath);

            if (xmlNEntity == null)
            {
                return null;
            }

            StringCollection strCollAttrib = new StringCollection();
            foreach(XmlNode child in xmlNEntity.ChildNodes)
            {
                if (child.LocalName == selection)
                {
                    strCollAttrib.Add(child.Attributes.GetNamedItem("name").ToString());
                }
            }

            returnList = new string[strCollAttrib.Count];
            strCollAttrib.CopyTo(returnList, 0);
            return returnList;  
        }
    }

    /// <summary>
    /// Location in registry
    /// </summary>
    public enum RegistryLocation
    {
        LocalMachine,
        CurrentUser,
    }

    /// <summary>
    /// Class dealing with the operations in the registry for CRUD operation of 
    /// server details / cache of search strings / meta data information.
    /// </summary>
    public static class CacheManager
    {
        static int limitOfCache = 10; // 10 is the default value. but will definitely be read from the cache. 

        /// <summary>
        /// Sets the value of meta data refresh bit (true - needs refresh) (false - no refresh needed)
        /// </summary>
        /// <param name="value"></param>
        public static void SetRefreshMDCacheBit(bool value)
        {
            RegistryKey cacheKey = GetRegistryForOfficeCRMCache();
            if (cacheKey != null)
            {
                cacheKey.SetValue("RefreshMDCache", ((value == true) ? ("1") : ("0")), RegistryValueKind.String);
                cacheKey.Close();
            }
            // dont know shud be put in some other place
            // anyway reading the limit of cache need to be done only once.
            // so i thot i'll do it here.
            limitOfCache = ConfigFileManager.CacheEntryLimit(limitOfCache);
        }

        /// <summary>
        /// Gets the value of meta data refresh bit (true - needs refresh) (false - no refresh needed)
        /// </summary>
        /// <returns></returns>
        public static bool GetRefreshMDCacheBit()
        {
            RegistryKey cacheKey = GetRegistryForOfficeCRMCache();
            if (cacheKey != null)
            {
                object refreshBit = cacheKey.GetValue("RefreshMDCache", "0");
                cacheKey.Close();
                return ((((string)refreshBit) == "0") ? (false) : (true));
            }
            return false;
        }

        /// <summary>
        /// Gets the basic key for the solution @"software\microsoft\MSCRM\Office_Integration"
        /// </summary>
        /// <param name="writeable"></param>
        /// <param name="rLoc"></param>
        /// <returns></returns>
        private static RegistryKey GetRegistryKeyForOfficeCRM(bool writeable, RegistryLocation rLoc)
        {
            RegistryKey key = null;
            try
            {
                if (writeable == true)
                {
                    if (rLoc == RegistryLocation.LocalMachine)
                    {
                        key = Registry.LocalMachine.OpenSubKey(@"software\microsoft\MSCRM\Office_Integration", RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.CreateSubKey);
                    }
                    if (rLoc == RegistryLocation.CurrentUser)
                    {
                        key = Registry.CurrentUser.OpenSubKey(@"software\microsoft\MSCRM\Office_Integration", RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.CreateSubKey);
                    }
                }
                else
                {
                    if (rLoc == RegistryLocation.LocalMachine)
                    {
                        key = Registry.LocalMachine.OpenSubKey(@"software\microsoft\MSCRM\Office_Integration");
                    }

                    if(rLoc == RegistryLocation.CurrentUser)
                    {
                        key = Registry.CurrentUser.OpenSubKey(@"software\microsoft\MSCRM\Office_Integration");
                    }

                }
            }
            catch // any exception.
            {
                Util.ShowError(global::CRMScenarios.Resources.InstallationCorrupted);
            }
            return key;
        }

        /// <summary>
        /// Gets the key for the cache
        /// </summary>
        /// <returns></returns>
        private static RegistryKey GetRegistryForOfficeCRMCache()
        {
            RegistryKey key = null;
            RegistryKey retKey = null;
            key = GetRegistryKeyForOfficeCRM(true, RegistryLocation.CurrentUser);

            try
            {
                retKey = key.OpenSubKey("Cache", true);
                if (retKey == null)
                {
                    retKey = key.CreateSubKey("Cache");
                }
            }
            catch
            {
                return null;
            }
            return retKey;
        }

        /// <summary>
        /// Gets the value of trace enabled or not.
        /// </summary>
        /// <returns></returns>
        public static bool IsTracingEnabled()
        {
            bool tracing = false;
            string tracingStr = GetRegistryValueFor("TraceCRMOffice", null, RegistryLocation.LocalMachine);
            if ((tracingStr != null) || (tracingStr != string.Empty))
            {
                tracing = true;
            }
            return tracing;
        }

        /// <summary>
        /// Gets the value of specified registry key 
        /// </summary>
        /// <param name="keyName"></param>
        /// <param name="defaultValue"></param>
        /// <param name="rLoc"></param>
        /// <returns></returns>
        private static string GetRegistryValueFor(string keyName, string defaultValue, RegistryLocation rLoc)
        {
            RegistryKey key = null;
            string regValue = defaultValue;
            if ((key = GetRegistryKeyForOfficeCRM(false, rLoc)) != null)
            {
                try
                {
                    regValue = (string)key.GetValue(keyName, defaultValue);
                }
                catch { }
                key.Close();
            }
            return regValue;
        }

        /// <summary>
        /// Gets the value of Server URL
        /// </summary>
        /// <param name="defaultResult"></param>
        /// <returns></returns>
        public static string GerServerURL(string defaultResult)
        {
            return GetRegistryValueFor("ServerURL", defaultResult, RegistryLocation.LocalMachine);            
        }

        /// <summary>
        /// Gets the value of Installation location
        /// </summary>
        /// <param name="defaultResult"></param>
        /// <returns></returns>
        public static string GetInstallPath(string defaultResult)
        {
            return GetRegistryValueFor("InstPath", defaultResult, RegistryLocation.LocalMachine);
        }

        /// <summary>
        /// Reads the set of search string entries for a given entitytype
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        private static StringCollection readCache(string entityType)
        {
            string[] strings = null;
            StringCollection ret = null;
            RegistryKey key = null;

            if ((entityType != null) && (key = GetRegistryForOfficeCRMCache()) != null)
            {
                try
                {
                    strings = (string[])key.GetValue(entityType);
                }
                catch { }
                key.Close(); 
            }
            if (strings != null)
            {
                ret = new StringCollection();
                foreach (string s in strings)
                {
                    ret.Add(s);
                }
            }

            return ret;
        }

        /// <summary>
        /// Writes the set of search string entries for a given entitytype
        /// </summary>
        /// <param name="cacheOfStrings"></param>
        /// <param name="entityType"></param>
        private static void writeCache(StringCollection cacheOfStrings, string entityType)
        {
            
            if (cacheOfStrings == null)
            {
                return;
            }

            RegistryKey key = null;
            string [] toWrite = new string[cacheOfStrings.Count];
            cacheOfStrings.CopyTo(toWrite, 0);

            if ((entityType != null) && (key = GetRegistryForOfficeCRMCache()) != null)
            {
                try
                {
                    key.SetValue(entityType, (object)toWrite, RegistryValueKind.MultiString);
                }
                catch (Exception)
                { }
                key.Close();
            }
            return;
        }

        /// <summary>
        /// Not in use. Does the action equiavalent for a Splay Tree. (not in full mode)
        /// </summary>
        /// <param name="cacheOfStrings"></param>
        /// <param name="lastSrchStr"></param>
        /// <returns></returns>
        private static string[] updateLocally(string[] cacheOfStrings, string lastSrchStr)
        {
            int i = 0;
            int j = 0;
            string[] retStrings0 = new string[limitOfCache];
            //string[] retStrings1;

            // logic comes here
            if ((cacheOfStrings == null) || (lastSrchStr == null))
            {
                return cacheOfStrings;
            }
            int length = cacheOfStrings.Length;
            int tmp = length - limitOfCache;
            int start = ((tmp) > 0) ? (tmp) : (0);
            bool found = false;

            /* 
             * BUG: if there are already 10 in the cache and the new one is not in the cache,
             * size of the cache will be 11 
             * 
             * Why not bothering abt it? - it is expensive.
             */

            for (i = start; i < length; i++)
            {
                if (cacheOfStrings[i] != lastSrchStr)
                {
                    retStrings0[j++] = cacheOfStrings[i];
                }
                else
                {
                    found = true;
                }
            }

            if (found)
            {
                retStrings0[j] = lastSrchStr;
            }
            else
            {
                int retLength = retStrings0.Length;

                for (i = 1; i < retLength; i++)
                {
                    retStrings0[i - 1] = retStrings0[i];
                }

                retStrings0[retLength] = lastSrchStr;
            }

            return retStrings0;
        }

        /// <summary>
        /// Not in use. the name says it all
        /// </summary>
        /// <param name="strs"></param>
        /// <returns></returns>
        private static string[] invertStrArray(string[] strs)
        {
            if (strs == null)
            {
                return null;
            }
            int length = strs.Length;
            string[] ret = new string[length];
            for (int j = 0; j < length; j++)
            {
                ret[j] = strs[length - j - 1];
            }
            return ret;
        }

        /// <summary>
        /// Add a new string to the collection. 
        /// add only if new. 
        /// else update the location of the string in the collection
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="newString"></param>
        /// <returns></returns>
        private static StringCollection AddTheNewStringTo(ref StringCollection collection, string newString)
        {
            if (collection == null)
            {
                collection = new StringCollection();
                collection.Add(newString);
                return collection;
            }

            if (collection.Contains(newString))
            {
                return collection;
            }

            if (collection.Count < limitOfCache)
            {
                collection.Add(newString);
                return collection;
            }
            collection.RemoveAt(0);
            collection.Add(newString);
            return collection;
        }

        /// <summary>
        /// Update the string cache for the entity and take back the updated one.
        /// </summary>
        /// <param name="lastSrchStr"></param>
        /// <param name="entiyType"></param>
        /// <returns></returns>
        public static StringCollection getUpdatedCache(string lastSrchStr, string entiyType)
        {
            if (entiyType == null)
            {
                return null;
            }

            StringCollection retCache = null;
            retCache = readCache(entiyType); // must be sync
            if (lastSrchStr == null)
            {
                return retCache;
                // return invertStrArray(retCache); - for the time being no ordering. 
                // just return the last 10
            }
            retCache = AddTheNewStringTo(ref retCache, lastSrchStr);
            // retCache = updateLocally(retCache, lastSrchStr);
            writeCache(retCache, entiyType);   // must be sync
            return retCache;
            // return invertStrArray(retCache);
        }
    }
}
